package RJ;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.TreeSet;
import cpsc433.Pair;
import cpsc433.PredicateReader;
import cpsc433.SisyphusPredicates;
import cpsc433.Predicate.ParamType;

public class Environment extends PredicateReader implements SisyphusPredicates{

	static Environment instance=null;
	public boolean fixedAssignments;
	public Solution currentSolution;

	public static PersonTable PT = new PersonTable();
	public static RoomTable RT = new RoomTable();
	public static ProjectTable PrjTable = new ProjectTable();
	public static GroupTable GT = new GroupTable();
	public static AssignmentTable AT = new AssignmentTable();
	
	private String[] room;
	private String[] person;
	private int numHead;	//number of heads(group-heads, project-heads, managers)
	private int roomSize, personSize;
	private int[] personIndexList;
	private int[] goodPersonIndex;
	private int goodValue;
	private long randSeed = 45;
	private Random randGen = new Random(randSeed);
	
	public Environment(String name) {
		super(name);
	}

	public static Environment get() {
		if(instance==null)
			instance= new Environment("ENV");
		return instance;
	}

	public static void reset() {
		PT = new PersonTable();
		RT = new RoomTable();
		PrjTable = new ProjectTable();
		GT = new GroupTable();
		AT = new AssignmentTable();		
	}




	@Override
	public void a_person(String p) {
		PT.insert(p);	
	}

	@Override
	public boolean e_person(String p) {		
		return PT.selectName(p);
	}

	@Override
	public void a_secretary(String p) {
		if(!e_person(p))
			a_person(p);
		PT.getPerson(p).setRole(ROLE.SECRETARY);		
	}

	@Override
	public boolean e_secretary(String p) {
		return PT.getPerson(p).getRole()==ROLE.SECRETARY;
	}

	@Override
	public void a_researcher(String p) {
		if(!e_person(p))
			a_person(p);
		PT.getPerson(p).setRole(ROLE.RESEARCHER);
	}

	@Override
	public boolean e_researcher(String p) {
		return PT.getPerson(p).getRole()==ROLE.RESEARCHER;
	}

	@Override
	public void a_manager(String p) {
		if(!e_person(p))
			a_person(p);
		PT.getPerson(p).setRole(ROLE.MANAGER);
	}

	@Override
	public boolean e_manager(String p) {
		return PT.getPerson(p).getRole()==ROLE.MANAGER;
	}

	@Override
	public void a_smoker(String p) {
		if(!e_person(p))
			a_person(p);
		PT.getPerson(p).setSmoker(true);
	}

	@Override
	public boolean e_smoker(String p) {
		return PT.getPerson(p).isSmoker();
	}

	@Override
	public void a_hacker(String p) {
		if(!e_person(p))
			a_person(p);
		PT.getPerson(p).setHacker(true);
	}

	@Override
	public boolean e_hacker(String p) {
		return PT.getPerson(p).isHacker();
	}

	@Override
	public void a_in_group(String p, String grp) {
		if(!e_person(p))
			a_person(p);
		if(!e_group(grp))
			a_group(grp);
		PT.getPerson(p).setGroup(GT.getGroup(grp));
	}
	public void a_group(String p, String grp) {
		a_in_group(p, grp);
	}
	@Override
	public boolean e_in_group(String p, String grp) {
		return PT.getPerson(p).getGroup().toString().equals(grp);
	}
	public boolean e_group(String p, String grp) {
		return e_in_group(p, grp);
	}

	@Override
	public void a_in_project(String p, String prj) {
		if(!e_person(p))
			a_person(p);
		if(!e_project(prj))
			a_project(prj);
		PT.getPerson(p).setProject(PrjTable.getProject(prj));
	}
	public void a_project(String p, String prj) {
		a_in_project(p,prj);
	}

	@Override
	public boolean e_in_project(String p, String prj) {
		return PT.getPerson(p).getProject().toString().equals(prj);
	}
	public boolean e_project(String p, String prj) {
		return e_in_project(p,prj);
	}
	@Override
	public void a_heads_group(String p, String grp) {
		if(!e_person(p))
			a_person(p);
		if(!e_group(grp))
			a_group(grp);
		GT.getGroup(grp).setGroupHead(PT.getPerson(p));
		PT.getPerson(p).setGroup(GT.getGroup(grp));
	}

	@Override
	public boolean e_heads_group(String p, String grp) {
		return GT.getGroup(grp).getGroupHead().toString().equals(p);
	}

	@Override
	public void a_heads_project(String p, String prj) {
		if(!e_person(p))
			a_person(p);
		if(!e_project(prj))
			a_project(prj);
		PrjTable.getProject(prj).setProjectHead(PT.getPerson(p));
		PT.getPerson(p).setProject(PrjTable.getProject(prj));
	}

	@Override
	public boolean e_heads_project(String p, String prj) {
		return PrjTable.getProject(prj).getProjectHead().toString().equals(p);
	}

	@Override
	public void a_works_with(String p, TreeSet<Pair<ParamType, Object>> p2s) {
		for (Pair<ParamType, Object> pair : p2s){
			assert(pair.getKey().equals(ParamType.STRING));
			a_works_with(p,(String)pair.getValue());
		}
	}

	@Override
	public boolean e_works_with(String p, TreeSet<Pair<ParamType, Object>> p2s) {
		for (Pair<ParamType, Object> pair : p2s){
			assert(pair.getKey().equals(ParamType.STRING));
			if(!e_works_with(p,(String)pair.getValue()))
				return false;
		}
		return true;
	}

	@Override
	public void a_works_with(String p, String p2) {
		if(!e_person(p))
			a_person(p);
		if(!e_person(p2))
			a_person(p2);
		PT.getPerson(p).WorkWith.add(PT.getPerson(p2));
		PT.getPerson(p2).WorkWith.add(PT.getPerson(p));		
	}

	@Override
	public boolean e_works_with(String p, String p2) {
		if(!e_person(p))
			return false;
		if(!e_person(p2))
			return false;
		
		return PT.getPerson(p).WorkWith.contains(PT.getPerson(p2));
	}

	@Override
	public void a_assign_to(String p, String room) throws Exception {
		if(!e_person(p))
			a_person(p);
		if(!e_room(room))
			a_room(room);
		
		AT.insert(PT.getPerson(p),RT.getRoom(room));
		
	}

	@Override
	public boolean e_assign_to(String p, String room) {
		if(!e_person(p))
			return false;
		if(!e_room(room))
			return false;
		return AT.selectName(PT.getPerson(p),RT.getRoom(room));
	}

	@Override
	public void a_room(String r) {
		RT.insert(r);
	}

	@Override
	public boolean e_room(String r) {
		return RT.selectName(r);
	}

	@Override
	public void a_close(String room, String room2) {
		if(!e_room(room))
			a_room(room);
		if(!e_room(room2))
			a_room(room2);
		RT.getRoom(room).CloseTo.add(RT.getRoom(room2));
		RT.getRoom(room2).CloseTo.add(RT.getRoom(room));
	}

	@Override
	public boolean e_close(String room, String room2) {
		if(!e_room(room))
			return false;
		if(!e_room(room2))
			return false;		
		return RT.getRoom(room).CloseTo.contains(RT.getRoom(room2));
	}

	@Override
	public void a_close(String room, TreeSet<Pair<ParamType, Object>> set) {
		for (Pair<ParamType, Object> pair : set){
			assert(pair.getKey().equals(ParamType.STRING));
			a_close(room,(String)pair.getValue());
		}

	}

	@Override
	public boolean e_close(String room, TreeSet<Pair<ParamType, Object>> set) {
		for (Pair<ParamType, Object> pair : set){
			assert(pair.getKey().equals(ParamType.STRING));
			if(!e_close(room,(String)pair.getValue()))
				return false;
		}
		return true;
	}

	@Override
	public void a_large_room(String r) {
		if(!e_room(r))
			a_room(r);
		RT.getRoom(r).setRoomSize(RoomSize.LARGEROOM);
	}

	@Override
	public boolean e_large_room(String r) {
		return RT.getRoom(r).getRoomSize()==RoomSize.LARGEROOM;
	}

	@Override
	public void a_medium_room(String r) {
		if(!e_room(r))
			a_room(r);
		RT.getRoom(r).setRoomSize(RoomSize.MEDIUMROOM);
	}

	@Override
	public boolean e_medium_room(String r) {
		return RT.getRoom(r).getRoomSize()==RoomSize.MEDIUMROOM;
	}

	@Override
	public void a_small_room(String r) {
		if(!e_room(r))
			a_room(r);
		RT.getRoom(r).setRoomSize(RoomSize.SMALLROOM);
	}

	@Override
	public boolean e_small_room(String r) {
		return RT.getRoom(r).getRoomSize()==RoomSize.SMALLROOM;
	}

	@Override
	public void a_group(String g) {
		GT.insert(g);
	}

	@Override
	public boolean e_group(String g) {
		return GT.selectName(g);
	}

	@Override
	public void a_project(String p) {
		PrjTable.insert(p);

	}

	@Override
	public boolean e_project(String p) {
		return PrjTable.selectName(p);
	}

	@Override
	public void a_large_project(String prj) {
		if(!e_project(prj))
			a_project(prj);
		PrjTable.getProject(prj).setLargeProject(true);

	}

	@Override
	public boolean e_large_project(String prj) {
		return PrjTable.getProject(prj).isLargeProject();
	}

	@Override
	public String toString(){
		System.out.println("Assignment Table: "+AT);
		System.out.println("Group Table: "+ GT);
		System.out.println("Project Table:"+ PrjTable);
		System.out.println("Person Table:"+ PT);
		System.out.println("Room Table:" + RT);
		return "";		
	}
	
	/*
	 * write the good resolution to output file
	 */
	public void writeSolution(String outputFileName){
		String s;
		System.out.println("Write solution to file: "+outputFileName + "\n");
		try{
			PrintWriter output = new PrintWriter(new FileWriter(outputFileName));
			for (Map.Entry<Person,Room> entry : AT.getAT().entrySet()) {
	            s ="assign-to("+entry.getKey().getName() + ","+ entry.getValue().getName() + ")";
	            //System.out.println(s);
	            output.println(s);
	        }
			output.close();					
		}
		catch (IOException ex){
			ex.printStackTrace();
		}		
	}	
	
	/*
	 * if a person is a group-head, or project-head, or manager, set 'isHead' true
	 */
	public void setHeadFlag(){
		Person p;
		for (Entry<String, Project> entry : PrjTable.getPrjTable().entrySet()){
            p = entry.getValue().getProjectHead();
            if (p != null) PT.getPerson(p.getName()).setIsHead(true);
        }	
		
		for (Entry<String, Group> entry : GT.getGT().entrySet()) {
            p = entry.getValue().getGroupHead();
            if (p != null) PT.getPerson(p.getName()).setIsHead(true);            
         }	    
		
		for (Entry<String, Person> entry : PT.getPT().entrySet()) {
            p = entry.getValue();
            if (p.getRole() == ROLE.MANAGER){
            	p.setIsHead(true);
            }           
            //if (p.isHead()) System.out.println("====Head is======" + p.getName());
        }			
	}
	
	/*
	 * order room array to large rooms, then medium rooms, then small rooms
	 * order person array to heads, non-smokers, smokers
	 */
	public void orderRoomAndPerson(){
		roomSize = RT.getSize();
		personSize = PT.getSize();
		room = new String[roomSize + 1];
		person = new String[personSize + 1];
				
		numHead = 0;
		int numNonS = 0, numSmoker = 0;
		
		setHeadFlag();		//set flag of head(group-head, project-head, manager)			
				
		String[] roomL = new String[roomSize + 1];
		String[] roomM = new String[roomSize + 1];
		String[] roomS = new String[roomSize + 1];				
		
		String[] personHead = new String[personSize + 1];
		String[] personNonSmoker = new String[personSize + 1];
		String[] personSmoker = new String[personSize + 1];		
		
		System.out.println("room amount is:\t\t" + roomSize);
		System.out.println("person amount is:\t" + personSize);		

		//rearrange room list: first large, then medium, last small ones
		int numL = 0, numM = 0, numS = 0;
		String s;
		for (Entry<String, Room> entry : RT.getRT().entrySet()) {
            Room r= entry.getValue();
            s=r.getName();
            
            if (r.getRoomSize().equals(RoomSize.LARGEROOM)){
            	roomL[numL] = s;
            	numL++;            	
            } else if (r.getRoomSize().equals(RoomSize.MEDIUMROOM)){
            	roomM[numM] = s;
            	numM++;
            } else{
            	roomS[numS] = s;
            	numS++;
            }                     
        }	
		
		int numRoom = 0;
		
		if (numL != 0){
			for (int i = 0; i < numL; i++){
				room[numRoom] = roomL[i];
				numRoom++;
				//System.out.println("Large room :\t" + roomL[i]);
			}
		}
		
		if (numM != 0){
			for (int i = 0; i < numM; i++){
				room[numRoom] = roomM[i];
				numRoom++;
				//System.out.println("Medium room :\t" + roomM[i]);
			}
		}	
		
		if (numS != 0){
			for (int i = 0; i < numS; i++){
				room[numRoom] = roomS[i];
				numRoom++;
				//System.out.println("Small room :\t" + roomS[i]);
			}
		}
						
		// rearrange person list: head first, then
		Person p;

		for (Entry<String, Person> entry : PT.getPT().entrySet()) {
            p = entry.getValue();
            if (p.isHead){
            	personHead[numHead] = p.getName();
            	numHead++;
            } else if (p.smoker) {
            	personSmoker[numSmoker] = p.getName();
            	numSmoker++;
            } else {
            	personNonSmoker[numNonS] = p.getName();
            	numNonS++;
            }
        }
		
		//merge person: head first, then non-smoker, last smoker
		int numPerson = 0;
		if (numHead != 0) {
			for (int i = 0; i < numHead; i++) {
				person[numPerson] = personHead[i];
				numPerson++;
			}
		}
		
		if (numNonS != 0) {
			for (int i = 0; i < numNonS; i++) {
				person[numPerson] = personNonSmoker[i];
				numPerson++;
			}
		}
		
		if (numSmoker != 0) {
			for (int i = 0; i < numSmoker; i++) {
				person[numPerson] = personSmoker[i];
				numPerson++;
			}
		}		
	}
	
	/*
	 * get the first solution, 
	 * room array: every room separates two slots
	 * person array: persons
	 * personIndexList: persons and empty slots
	 */
	public boolean getFirstSolution(){
		boolean isSolved = false;
		
		orderRoomAndPerson();
		if (roomSize == 0) {
			isSolved = false;
			System.out.println("No solution, since room Size is 0.");
		} else if (personSize == 0){
			isSolved = false;
			System.out.println("No solution, since person Size is 0.");
		}else{
			isSolved = true;
			personIndexList = new int[2*roomSize + 2];
			goodPersonIndex = new int[2*roomSize + 2];
			
			//Now has rearranged persons and rooms
			//build a person list that add zero(empty) and total length 2*roomSize
			String tempP,tempR;
			//set value
			int diff = 2*roomSize - personSize;
			if (diff < numHead) {
				System.out.println("====== not be solved!");
				System.out.println("Since there are not enough rooms for persons.");
				isSolved = false;
			}else{
				isSolved = true;
				System.out.println("---- be solved");
				System.out.println("\t...continuing to search better one...\n");
				//System.out.println("difference is: \t" + diff);
				
				int roomIndex = 0;
				int personIndex = 0;			
				
				if ( diff > 0) {
					for (int j = 0; j < diff; j++){
						personIndexList[roomIndex] = personIndex;
						roomIndex++;
						personIndex++;
						personIndexList[roomIndex] = -1;
						roomIndex++;
					}
				}
								
				while (roomIndex < 2*roomSize ){
					personIndexList[roomIndex] = personIndex;
					roomIndex++;
					personIndex++;				
				}
				
				for (int i = 0; i < 2*roomSize; i++){
					if (personIndexList[i] >= 0) {
						tempP = person[personIndexList[i]];
						tempR = room[i/2];
						try{					
							a_assign_to(tempP, tempR);
						}catch (Exception ex){
							ex.printStackTrace();
						}
					}
				}
			
				Evaluator ev = new Evaluator(AT, GT, PrjTable, PT);
	
				goodValue = ev.value();
				for (int i = 0; i < 2*roomSize; i++){
					goodPersonIndex[i] = personIndexList[i];
				}
			}
		}
		return isSolved;
	}
	
	/*
	 * Generate the next solution.
	 * Based on the first solution, do a swap to create new one.
	 * First, generate 2 random integers, satisfied some conditions,
	 * then exchange two persons in these two positions.
	 * If either of them is a head, exchange the whole room(two slots)
	 */
	public void getNextSolution(){
		String tempP,tempR;
		int sr, tr;
		int range = 2 * roomSize;
		if (roomSize == 0) {
			range = 1;
		}
		boolean eFlag = false;			
		do{
			sr = randGen.nextInt(range);		//generate [0, (2* roomsize-1)]
			tr = randGen.nextInt(range);
			
			if (sr == tr) eFlag = false;
			else if ( ((sr - tr) == 1) && (((sr/2) * 2) != sr)) eFlag = false;
			else if ( ((tr - sr) == 1) && (((tr/2) * 2) != tr)) eFlag = false;
			else if ((personIndexList[sr] == -1) && (personIndexList[tr] == -1))
						eFlag = false;		// not both empty
			else eFlag = true;								
		}while (!eFlag);
		
		//System.out.println("Random value is " + sr + "," + tr);
		
		int temp = personIndexList[sr];
		personIndexList[sr] = personIndexList[tr];
		personIndexList[tr] = temp;
		
		
		if (personIndexList[sr] >= 0) {
			tempP = person[personIndexList[sr]];
			tempR = room[sr/2];
			try{					
				a_assign_to(tempP, tempR);
			}catch (Exception ex){
				ex.printStackTrace();
			};
		}
		
		if (personIndexList[tr] >= 0) {
			tempP = person[personIndexList[tr]];
			tempR = room[tr/2];
			try{					
				a_assign_to(tempP, tempR);
			}catch (Exception ex){
				ex.printStackTrace();
			};
		}
		
		int swap1, swap2;
		if (personIndexList[sr] >= 0){
			String tempS = person[personIndexList[sr]];
			if ( PT.getPerson(tempS).isHead()) {
				if (sr == ((sr/2) * 2)) swap1 = sr + 1;		//even
				else swap1 = sr - 1;						//odd
				if (tr == ((tr/2) * 2)) swap2 = tr + 1;		//even
				else swap2 = tr - 1;						//odd
				
				//swap swap1&swap2
				temp = personIndexList[swap1];
				personIndexList[swap1] = personIndexList[swap2];
				personIndexList[swap2] = temp;		
				
				if (personIndexList[swap1] >= 0) {
					tempP = person[personIndexList[swap1]];
					tempR = room[swap1/2];
					try{					
						a_assign_to(tempP, tempR);
					}catch (Exception ex){
						ex.printStackTrace();
					};
				}
				
				if (personIndexList[swap2] >= 0) {
					tempP = person[personIndexList[swap2]];
					tempR = room[swap2/2];
					try{					
						a_assign_to(tempP, tempR);
					}catch (Exception ex){
						ex.printStackTrace();
					};
				}
				
			}
		}
		Evaluator ev = new Evaluator(AT, GT, PrjTable, PT);
		int value = ev.value();
		//System.out.println("value is: \t" + value);	
		
		if (goodValue < value) {
			goodValue = value;
			for (int i = 0; i < 2*roomSize; i++){
				goodPersonIndex[i] = personIndexList[i];
			}
		}
	}
	
	public void getGoodSolution(){
		String tempP, tempR;
		for (int i = 0; i < 2*roomSize; i++){
			if (goodPersonIndex[i] >= 0) {
				tempP = person[goodPersonIndex[i]];
				tempR = room[i/2];									
				try{					
					a_assign_to(tempP, tempR);
				}catch (Exception ex){
					ex.printStackTrace();
				};
			}
		}
	}
	

	/*
	 * assign persons to rooms
	 */
	public void assignPersonToRoom(int timeLimit, String outputFileName) {
			//throws Exception{
		
		long startTime = System.currentTimeMillis();

		System.out.println("*****************");
		if (!getFirstSolution()) {
			System.out.println("----No solution.");			
		} else{

			//***********************
			boolean timeEndFlag;
			long countTimes = 0;		
			long partTimeLimit = (timeLimit ) * 95 /1000;  		//95%, 10 parts
			long timeUsed = startTime;
			long tempTime;
			
			int[] savedValue = new int[11];
			int[][] savedGoodSolution = new int[10][2*roomSize + 2];
			
			for (int i = 0; i < 10; i++){
				getNextSolution();
				for (int j = 0; j < 2*roomSize; j++){
					savedGoodSolution[i][j]= goodPersonIndex[j];
					savedValue[i] = goodValue;
				}				
			}
			
			for (int i = 0; i < 10; i++){
				goodValue = savedValue[i];
				for (int j = 0; j < 2 * roomSize; j++){
					goodPersonIndex[j] = savedGoodSolution[i][j];
				}
								
				timeEndFlag = false;

				while (!timeEndFlag){
					countTimes++;
					getNextSolution();
					
					tempTime = System.currentTimeMillis();
					if ( (tempTime - timeUsed) > partTimeLimit) {
						timeUsed = tempTime;
						timeEndFlag = true;	
					}
				}		
				
				for (int j = 0; j < 2*roomSize; j++){
					savedGoodSolution[i][j]= goodPersonIndex[j];
					savedValue[i] = goodValue;
				}		
				
			}
			
			int maxIndex = 0;
			int maxValue = savedValue[0];
			for (int i = 0; i < 10; i++){
				if (maxValue < savedValue[i]){
					maxIndex = i;
					maxValue = savedValue[i];
				}	
				
				//System.out.println("savedValue[" + i + "] = " +savedValue[i]);
			}
			
			//System.out.println("maxIndex is " + maxIndex);
			
			goodValue = savedValue[maxIndex];
			for (int j = 0; j < 2 * roomSize; j++){
				goodPersonIndex[j] = savedGoodSolution[maxIndex][j];
			}
			
			getGoodSolution();
			
			
			
			System.out.println(" ##### time limit is: \t" + timeLimit + " ms");
			System.out.println(" ##### real time is  : \t" + (System.currentTimeMillis() - startTime) + " ms");
			System.out.println(" ##### search times: \t" + countTimes);
			System.out.println(" ##### good value is : \t" + goodValue + "\n\n");
			
			writeSolution(outputFileName);
					
			System.out.println("-----The End!-----");
			}
	}		
}
